package flexibilityDesign;

import ilog.concert.IloLinearNumExpr;
import ilog.concert.IloNumVar;
import ilog.cplex.IloCplex;

import java.math.BigDecimal;

import util.NullOutputStream;
import concurrent.Job;

public class DualSolver extends Job<DualResult> {
	private double[] demand;
	private int n;
	private int m;
	private double[] capacities;
	private int[][] YValue;
	private double[][] profit_m;

	private double[][] corePoint = null;

	private int taskIndex = 0;

	public DualSolver(int n, int m, double[] demand, int[][] Yvalue, double[][] profit, double[] capacities,
			int taskIndex) {
		this.m = m;
		this.n = n;
		this.demand = demand;
		this.YValue = Yvalue;
		this.profit_m = profit;
		this.capacities = capacities;
		this.taskIndex = taskIndex;
	}

	public DualSolver(int n, int m, double[] demand, int[][] Yvalue, double[][] corePoint, double[][] profit,
			double[] capacities, int taskIndex) {
		this.m = m;
		this.n = n;
		this.demand = demand;
		this.YValue = Yvalue;
		this.profit_m = profit;
		this.capacities = capacities;
		this.taskIndex = taskIndex;
		this.corePoint = corePoint;
	}

	@Override
	public DualResult execute() {
		DualResult result = new DualResult();
		try {
			if (corePoint == null) {
				IloCplex dual = new IloCplex();
				dual.setOut(new NullOutputStream());
				IloNumVar[] phi = dual.numVarArray(m, Double.NEGATIVE_INFINITY, 0.0);
				IloNumVar[] sigma = dual.numVarArray(n, Double.NEGATIVE_INFINITY, 0.0);
				IloNumVar[][] gamma = new IloNumVar[n][m];
				for (int i = 0; i < n; i++)
					for (int j = 0; j < m; j++)
						gamma[i][j] = dual.numVar(Double.NEGATIVE_INFINITY, 0.0);

				double[] phiVal = new double[m];
				double[] sigmaVal = new double[n];
				double[][] gammaVal = new double[n][m];
				double dualObjVal = 0.0;

				IloLinearNumExpr dualObj = dual.linearNumExpr();
				for (int j = 0; j < m; j++)
					dualObj.addTerm(capacities[j], phi[j]);
				for (int i = 0; i < n; i++)
					dualObj.addTerm(demand[i], sigma[i]);
				for (int i = 0; i < n; i++) {
					for (int j = 0; j < m; j++) {
						if (corePoint != null) {
							dualObj.addTerm(capacities[j] * corePoint[i][j], gamma[i][j]);
						} else if (YValue[i][j] == 1)
							dualObj.addTerm(capacities[j], gamma[i][j]);

						// constraint
						dual.addLe(dual.sum(phi[j], sigma[i], gamma[i][j]), -profit_m[i][j]);
					}
				}
				dual.addMaximize(dualObj);

				if (dual.solve()) {
					dualObjVal = dual.getObjValue();

					sigmaVal = dual.getValues(sigma);
					phiVal = dual.getValues(phi);
					for (int i = 0; i < n; i++)
						gammaVal[i] = dual.getValues(gamma[i]);

				}

				BigDecimal et = BigDecimal.ZERO;
				BigDecimal[][] Et = new BigDecimal[n][m];
				for (int i = 0; i < n; i++) {
					for (int j = 0; j < m; j++)
						Et[i][j] = BigDecimal.ZERO;
				}

				for (int i = 0; i < n; i++) {
					et = et.add(BigDecimal.valueOf(sigmaVal[i] * demand[i]));
				}
				for (int j = 0; j < m; j++) {
					et = et.add(BigDecimal.valueOf(phiVal[j] * capacities[j]));
				}
				for (int i = 0; i < n; i++) {
					for (int j = 0; j < m; j++) {
						Et[i][j] = Et[i][j].add(BigDecimal.valueOf(gammaVal[i][j] * (-capacities[j])));
					}
				}
				result.setEt(Et);
				result.setEtm(et);
				result.setObjValue(dualObjVal);
				result.setTaskIndex(taskIndex);

				dual.end();
			}

			if (corePoint != null) {
				// solve the sub-problem
				IloCplex subproblem = new IloCplex();
				subproblem.setOut(new NullOutputStream());
				IloNumVar[][] x = new IloNumVar[n][m];
				for (int i = 0; i < n; i++)
					for (int j = 0; j < m; j++)
						x[i][j] = subproblem.numVar(0.0, Double.MAX_VALUE);
				IloLinearNumExpr obj = subproblem.linearNumExpr();
				for (int i = 0; i < n; i++)
					for (int j = 0; j < m; j++)
						obj.addTerm(-profit_m[i][j], x[i][j]);
				subproblem.addMinimize(obj);
				for (int i = 0; i < n; i++) {
					IloLinearNumExpr dl = subproblem.linearNumExpr();
					for (int j = 0; j < m; j++)
						dl.addTerm(1.0, x[i][j]);
					subproblem.addLe(dl, demand[i]);
				}
				for (int j = 0; j < m; j++) {
					IloLinearNumExpr cl = subproblem.linearNumExpr();
					for (int i = 0; i < n; i++)
						cl.addTerm(1.0, x[i][j]);
					subproblem.addLe(cl, capacities[j]);
				}
				for (int i = 0; i < n; i++) {
					for (int j = 0; j < m; j++) {
						subproblem.addLe(x[i][j], YValue[i][j] * capacities[j]);
					}
				}
				double subProObjValue = 0.0;
				if (subproblem.solve()) {
					subProObjValue = subproblem.getObjValue();
				}
				subproblem.end();

				// subsequently solve the following model to get the maximal cut
				IloCplex coreDual = new IloCplex();
				coreDual.setOut(new NullOutputStream());
				IloNumVar[] corephi = coreDual.numVarArray(m, Double.NEGATIVE_INFINITY, 0.0);
				IloNumVar[] coresigma = coreDual.numVarArray(n, Double.NEGATIVE_INFINITY, 0.0);
				IloNumVar[][] coregamma = new IloNumVar[n][m];
				for (int i = 0; i < n; i++)
					for (int j = 0; j < m; j++)
						coregamma[i][j] = coreDual.numVar(Double.NEGATIVE_INFINITY, 0.0);
				double[] phiVal = new double[m];
				double[] sigmaVal = new double[n];
				double[][] gammaVal = new double[n][m];

				IloLinearNumExpr coreDualObj = coreDual.linearNumExpr();
				IloLinearNumExpr coreEqual = coreDual.linearNumExpr();

				for (int j = 0; j < m; j++) {
					coreDualObj.addTerm(capacities[j], corephi[j]);
					coreEqual.addTerm(capacities[j], corephi[j]);
				}
				for (int i = 0; i < n; i++) {
					coreDualObj.addTerm(demand[i], coresigma[i]);
					coreEqual.addTerm(demand[i], coresigma[i]);
				}
				for (int i = 0; i < n; i++) {
					for (int j = 0; j < m; j++) {
						coreDualObj.addTerm(corePoint[i][j] * capacities[j], coregamma[i][j]);

						if (YValue[i][j] == 1)
							coreEqual.addTerm(YValue[i][j] * capacities[j], coregamma[i][j]);

						// constraint
						coreDual.addLe(coreDual.sum(corephi[j], coresigma[i], coregamma[i][j]), -profit_m[i][j]);
					}
				}
				coreDual.addMaximize(coreDualObj);

				coreDual.addEq(coreEqual, subProObjValue);

				if (coreDual.solve()) {
					// dualObjVal = coreDual.getObjValue();
					sigmaVal = coreDual.getValues(coresigma);
					phiVal = coreDual.getValues(corephi);
					for (int i = 0; i < n; i++)
						gammaVal[i] = coreDual.getValues(coregamma[i]);
				}

				BigDecimal et = BigDecimal.ZERO;
				BigDecimal[][] Et = new BigDecimal[n][m];
				for (int i = 0; i < n; i++) {
					for (int j = 0; j < m; j++)
						Et[i][j] = BigDecimal.ZERO;
				}

				for (int i = 0; i < n; i++) {
					et = et.add(BigDecimal.valueOf(sigmaVal[i] * demand[i]));
				}
				for (int j = 0; j < m; j++) {
					et = et.add(BigDecimal.valueOf(phiVal[j] * capacities[j]));
				}
				for (int i = 0; i < n; i++) {
					for (int j = 0; j < m; j++) {
						Et[i][j] = Et[i][j].add(BigDecimal.valueOf(gammaVal[i][j] * (-capacities[j])));
					}
				}
				result.setEt(Et);
				result.setEtm(et);
				result.setObjValue(subProObjValue);
				result.setTaskIndex(taskIndex);

				coreDual.end();
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

}
